前面三天,我們已經做了幾件事情:
Day 1:為什麼系統工程師需要自動化
Day 2:PowerShell 基本操作
Day 3:Object 與 Pipeline
到目前為止,大部分結果都還停留在 PowerShell 視窗裡。
例如:
Get-Service |
Where-Object Status -eq "Running" |
Select-Object Name, DisplayName, Status
可以很快知道現在有哪些 Service 正在執行。
但實際工作不太可能把 PowerShell 視窗截圖之後直接交出去。
很多維運工作最後都會變成:
查詢資料
↓
整理資料
↓
產生報表
↓
交給其他人確認
↓
後續追蹤
例如:
AD 帳號盤點
電腦物件盤點
Server 磁碟空間
Service 狀態
使用者最後登入時間
資產清冊
異常清單
最後通常都需要一份可以用 Excel 開啟的檔案。
所以 Day 4 我們就來做第一份真正可以留下來的維運報表。
今天的目標
今天主要會用到兩個 Cmdlet:
Export-Csv
Import-Csv
最後會完成這個流程:
Get-Service
↓
Where-Object
↓
Select-Object
↓
Export-Csv
↓
CSV 報表
↓
Excel
而且我們不只把資料輸出去。
還會試著把 CSV 再讀回 PowerShell,看看之後怎麼拿既有報表繼續做自動化。
先產生最簡單的 CSV
我們先從熟悉的 Get-Service 開始。
執行:
Get-Service |
Select-Object Name, DisplayName, Status
會看到類似:
Name DisplayName Status
Appinfo Application Information Running
BITS Background Intelligent... Stopped
EventLog Windows Event Log Running
W32Time Windows Time Running
接下來把結果輸出成 CSV。
Get-Service |
Select-Object Name, DisplayName, Status |
Export-Csv C:\Temp\Services.csv -NoTypeInformation -Encoding UTF8
如果成功,就會產生:
C:\Temp\Services.csv
使用 Excel 開啟,就可以看到:
Name DisplayName Status
Appinfo Application Information Running
BITS Background Intelligent Transfer Service Stopped
EventLog Windows Event Log Running
這已經是我們第一份 PowerShell 維運報表。
如果 C:\Temp 不存在怎麼辦?
這也是寫 Script 很常碰到的問題。
如果:
C:\Temp
不存在,Export-Csv 就會失敗。
所以正式一點的寫法,可以先確認資料夾:
$ReportPath = "C:\Temp"
if (-not (Test-Path $ReportPath)) {
New-Item -Path $ReportPath -ItemType Directory
}
這裡第一次稍微碰到 if。
意思其實很簡單:
檢查 C:\Temp
↓
不存在?
↓
建立 C:\Temp
其中:
Test-Path
是拿來確認路徑是否存在。
例如:
Test-Path C:\Temp
如果存在:
True
不存在:
False
Export-Csv 到底在做什麼?
回到這條:
Get-Service |
Select-Object Name, DisplayName, Status |
Export-Csv C:\Temp\Services.csv -NoTypeInformation -Encoding UTF8
可以拆成:
Get-Service
↓
取得 Service Object
Select-Object
↓
只留下需要的 Property
Export-Csv
↓
把 Object 寫進 CSV
這裡最重要的其實還是前幾天一直在講的:
PowerShell 處理的是 Object。
假設一筆資料是:
Name = W32Time
DisplayName = Windows Time
Status = Running
輸出 CSV 之後,就會變成類似:
"Name","DisplayName","Status"
"W32Time","Windows Time","Running"
CSV 本質上其實就是文字檔。
只是使用:
Comma
,
分隔不同欄位。
所以 CSV 全名就是:
Comma-Separated Values
為什麼需要 -NoTypeInformation?
我們剛剛使用:
-NoTypeInformation
這個參數在 Windows PowerShell 5.1 很常看到。
目的就是不要在 CSV 開頭加入物件類型資訊。
因此我通常會寫:
Export-Csv -Path "C:\Temp\Services.csv"
-NoTypeInformation `
-Encoding UTF8
這樣產生的檔案比較乾淨,也比較適合直接拿去開。
不要把 Format-Table 接在 Export-Csv 前面
Day 3 有稍微提到這件事情,今天可以實際再看一次。
很多人第一次輸出報表會直覺寫:
Get-Service |
Format-Table Name, Status |
Export-Csv C:\Temp\Services.csv
因為在螢幕上:
Format-Table
看起來很好。
但這個概念其實不對。
Format-Table 是:
給人看的格式。
Select-Object 是:
資料本身。
所以:
Get-Service
↓
Select-Object
↓
Export-Csv
才是比較正確的資料處理方式。
第一次做真正的 Service 巡檢報表
接下來不要把全部 Service 都輸出去。
假設今天我們的需求是:
找出 StartType 為 Automatic,但目前不是 Running 的 Service。
可以寫:
Get-Service |
Where-Object {
$.StartType -eq "Automatic" -and
$.Status -ne "Running"
} |
Select-Object Name, DisplayName, Status, StartType |
Export-Csv "C:\Temp\Service_Check.csv" -NoTypeInformation
-Encoding UTF8
產生:
C:\Temp\Service_Check.csv
打開後可能看到:
Name DisplayName Status StartType
MapsBroker Downloaded Maps Manager Stopped Automatic
WSearch Windows Search Stopped Automatic
不過跟 Day 3 一樣要注意:
出現在這份清單,不代表一定故障。
這份報表比較像是:
需要進一步確認的 Service 清單。
因為有些 Windows Service 本來就可能受到 Trigger Start 或其他系統機制控制。
Automation 可以先幫我們:
幾百個 Service
↓
條件篩選
↓
剩下幾個需要確認
而不是讓工程師從頭到尾自己找。
報表最好不要每次都叫 Services.csv
如果每天都執行:
Services.csv
第二天可能直接把第一天的報表蓋掉。
所以正式一點,我會把日期放進檔名。
PowerShell 可以取得現在時間:
Get-Date
例如:
2026年9月12日 上午 06:30:00
但這個格式不能直接拿來當檔名。
所以可以:
Get-Date -Format "yyyyMMdd"
結果:
20260912
存成變數:
$Date = Get-Date -Format "yyyyMMdd"
然後:
$FileName = "Service_Check_$Date.csv"
結果就是:
Service_Check_20260912.csv
組合完整路徑
可以再往前一步:
$Date = Get-Date -Format "yyyyMMdd"
$ReportFolder = "C:\Temp"
$ReportFile = "$ReportFolder\Service_Check_$Date.csv"
這時候:
$ReportFile
會得到:
C:\Temp\Service_Check_20260912.csv
最後:
Get-Service |
Where-Object {
$.StartType -eq "Automatic" -and
$.Status -ne "Running"
} |
Select-Object Name, DisplayName, Status, StartType |
Export-Csv $ReportFile -NoTypeInformation -Encoding UTF8
這就開始比較像真的 Script 了。
再加上電腦名稱
如果今天只跑一台電腦,看:
Service_Check_20260912.csv
可能還知道是哪台。
但是未來如果:
Server01
Server02
Server03
Server04
同時產生報表,裡面最好直接留下 Hostname。
Windows 的 Computer Name 可以透過:
$env:COMPUTERNAME
例如:
SERVER01
問題來了。
Get-Service 裡面沒有我們想要的 ComputerName 欄位。
那要怎麼加?
可以使用:
Select-Object
建立自訂欄位。
第一次建立自訂欄位
例如:
Get-Service |
Select-Object `
@{Name="ComputerName"; Expression={$env:COMPUTERNAME}},
Name,
DisplayName,
Status
結果:
ComputerName Name DisplayName Status
SERVER01 Appinfo Application Information Running
SERVER01 BITS Background Intelligent... Stopped
這一段:
@{
Name="ComputerName"
Expression={$env:COMPUTERNAME}
}
可以先簡單理解為:
我要自己建立一個叫 ComputerName 的欄位。
而這個欄位的值是:
$env:COMPUTERNAME
後面做報表時會非常常用。
再加入報表產生時間
同樣的方法也可以加入:
CheckTime
例如:
Get-Service |
Select-Object `
@{Name="ComputerName"; Expression={$env:COMPUTERNAME}},
@{Name="CheckTime"; Expression={Get-Date}},
Name,
DisplayName,
Status,
StartType
現在資料就完整很多:
ComputerName CheckTime Name Status StartType
SERVER01 2026/09/12 06:30 EventLog Running Automatic
SERVER01 2026/09/12 06:30 BITS Stopped Manual
這時候它就不只是 PowerShell 查詢結果。
而開始比較像:
維運紀錄。
把今天內容組成第一支完整 Script
現在把前面全部串起來。
$ReportFolder = "C:\Temp"
if (-not (Test-Path $ReportFolder)) {
New-Item -Path $ReportFolder -ItemType Directory
}
$Date = Get-Date -Format "yyyyMMdd"
$ReportFile = "$ReportFolder\Service_Check_$Date.csv"
$Services = Get-Service |
Where-Object {
$.StartType -eq "Automatic" -and
$.Status -ne "Running"
} |
Sort-Object Name |
Select-Object `
@{Name="ComputerName"; Expression={$env:COMPUTERNAME}},
@{Name="CheckTime"; Expression={Get-Date}},
Name,
DisplayName,
Status,
StartType
$Services |
Export-Csv -Path $ReportFile
-NoTypeInformation `
-Encoding UTF8
Write-Host "Report completed:"
Write-Host $ReportFile
執行之後:
Report completed:
C:\Temp\Service_Check_20260912.csv
到這裡,我們已經第一次從:
PowerShell Command
變成:
PowerShell Script
↓
資料收集
↓
條件篩選
↓
整理欄位
↓
建立報表
Export 完之後,也要會 Import
產生 CSV 只是第一步。
真正有趣的是:
CSV 還可以重新讀回 PowerShell。
例如:
$Report = Import-Csv "C:\Temp\Service_Check_20260912.csv"
然後:
$Report
就可以看到資料。
甚至可以繼續:
$Report |
Where-Object Status -eq "Stopped"
或:
$Report |
Select-Object ComputerName, Name, Status
也就是:
CSV
↓
Import-Csv
↓
Object
↓
Where
↓
Sort
↓
Select
有沒有發現又回到 Day 3?
這就是 Object 與 Pipeline 的好處。
Import-Csv 讀進來之後又是 Object
可以實際確認:
Import-Csv "C:\Temp\Service_Check_20260912.csv" |
Get-Member
你會發現 PowerShell 又可以看到:
ComputerName
CheckTime
Name
DisplayName
Status
StartType
這表示我們可以:
$Report = Import-Csv "C:\Temp\Service_Check_20260912.csv"
然後:
$Report.Name
直接取得所有 Service Name。
不用自己處理:
逗號在哪裡?
第幾欄?
字串怎麼切?
PowerShell 已經幫我們把 CSV 轉回可以操作的 Object。
為什麼 Import-Csv 對企業維運很好用?
因為很多企業環境的資料其實都長這樣:
EmployeeID,Username,Department
24983,user01,IT
24984,user02,HR
24985,user03,Finance
例如人資提供:
離職人員.csv
我們可以:
$Users = Import-Csv "C:\Temp\DisabledUsers.csv"
接著:
foreach ($User in $Users) {
$User.Username
}
未來甚至可以變成:
HR CSV
↓
PowerShell
↓
找 AD User
↓
確認帳號
↓
停用帳號
↓
寫 Log
↓
產生報表
這就是為什麼我把 CSV 放得這麼前面。
因為它是企業 IT 自動化裡非常實用的資料交換方式。
CSV 不是 Excel
這邊也順便釐清一個常見觀念。
CSV ≠ Excel
CSV 只是:
文字格式的表格資料。
Excel 可以開 CSV,所以平常很容易把兩者混在一起。
CSV 沒有:
字體
顏色
多個工作表
儲存格格式
圖表
Excel Formula 格式能力
但它有一個很大的好處:
簡單。
PowerShell 很容易:
讀取
修改
過濾
輸出
所以做系統維運時,我反而非常常使用 CSV。
如果只是要:
盤點
清單
稽核
比對
匯入
匯出
CSV 通常就已經足夠。
中文變亂碼怎麼辦?
如果報表裡未來出現:
中文姓名
部門
備註
電腦名稱
Encoding 就會變得重要。
所以 Windows PowerShell 5.1 我通常會明確寫:
-Encoding UTF8
例如:
Export-Csv -Path "C:\Temp\Report.csv"
-NoTypeInformation `
-Encoding UTF8
如果使用 PowerShell 7,而 CSV 使用 Excel 開啟時遇到中文編碼問題,可以再檢查 PowerShell 版本與 CSV 編碼;新版環境也可以考慮使用適合 Excel 的 UTF-8 BOM 輸出方式。
這部分之後真的遇到再處理就好。
目前先養成:
輸出包含中文的資料時,要注意 Encoding。
今天真正學到的其實不只是 Export-Csv
到目前為止,我們已經開始把前幾天的東西串起來:
Get-Service
↓
Object
Where-Object
↓
篩選
Sort-Object
↓
排序
Select-Object
↓
整理欄位
Export-Csv
↓
保存資料
Import-Csv
↓
重新載入
Object
↓
繼續處理
這套流程之後換成 Active Directory 幾乎完全一樣。
例如:
Get-ADUser
未來可以變成:
Get-ADUser
↓
篩選 Inactive User
↓
選 Username / LastLogonDate / Enabled
↓
Export-Csv
↓
AD 帳號盤點報表
所以今天看起來只是做一個:
Services.csv
其實是在建立後面 AD 自動化很重要的基礎。
Day 4 小結
今天我們正式做出了系列中的第一份維運報表。
從原本:
Get-Service
一路做到:
System Data
↓
PowerShell Object
↓
Filter
↓
Sort
↓
Select
↓
CSV
↓
Excel
也開始碰到幾個實際寫 Script 很重要的觀念:
Test-Path
New-Item
Get-Date
Export-Csv
Import-Csv
以及自訂欄位:
@{
Name="ComputerName"
Expression={$env:COMPUTERNAME}
}
對我來說,從今天開始 PowerShell 才比較真的進入「維運工具」的階段。
因為查詢結果不再只是執行完就消失。
我們開始可以把它:
留下來、整理、比較、交付,以及作為下一個自動化流程的輸入。